package com.triangulator;

import java.io.File;
import java.io.FileNotFoundException;

import android.location.Location;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Toast;

import java.io.FileOutputStream;
import java.util.StringTokenizer;

import com.ece594bb.project.localization_and_reidentification.R;
import com.triangulator.bluetooth.BluetoothChatService;
import com.triangulator.gps.LatLong;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;

public class Reidentificator extends Triangulator {
	private static String TAG="Reidentificator";

	private String fileDir = "/sdcard/triangulator/";
	private String fileName = "Image";
	private String fileExt = ".jpg";
	private volatile int lastFile=0;
	
	private static final float mFx = 1966.0f;
	
	public static int mScaleWidth = 160;
	public static int mScaleHeight = 120;
	public static float mScaleWidthRatio;
	private static int mCropWidth = 20;
	private static int mCropHeight = 20;
	private int mSearchStepX = 10;
	private int mSearchStepY = 10;
	
	private HVHistogram mPatchHist;
	
	private boolean hasCameraBtnBeenReleased;
	private boolean hasFocusBtnBeenReleased;
	private boolean disableAllButtons;
	private boolean amIMaster;
	private boolean amISlave;
	private volatile boolean receivedHSVinfo;
	private volatile boolean receivedMatch;
	
	private volatile int match_y;
	private volatile int match_x;
	
	private Toast notConnected;
	private Toast waiting;
	private Toast noGps;
	
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);

		// Set up the custom title
		mTitleLeft.setText("Re-Identificator");
		
		if(!(new File(fileDir)).exists())
		{
			if(!(new File(fileDir)).mkdir())
				Log.e(TAG, "problem creating folder");	
			else
				Log.w(TAG, "folder created");	
		}
		else
			Log.v(TAG, "folder already exists");	


//		while ((new File(fileDir + fileName + lastFile + fileExt)).exists())
//		{
//			lastFile++;
//		}

		Log.i(TAG, "File to add: " + lastFile);
		
		notConnected = Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT);
		
		//centerPanel.disableMaps(true);
		centerPanel.drawRectCenter();

		waiting = Toast.makeText(Reidentificator.this, "Processing image", Toast.LENGTH_LONG);
		noGps = Toast.makeText(Reidentificator.this, R.string.no_gps_no_internet, Toast.LENGTH_SHORT);
		
		btn_restart.setOnClickListener(new View.OnClickListener() {
			public void onClick(View arg0) {
				
				centerPanel.drawRectCenter();
				//btn_restart.setVisibility(View.GONE);
				
				CameraSurfaceView.mCamera.startPreview();
				CameraSurfaceView.inPreview = true;
				
				allMyLocations.clear();
				allOtherLocations.clear();
				updateCenterText();
			}
		});
		
		takePicture.setOnClickListener(new View.OnClickListener() {
			public void onClick(View arg0) {
				doTheMasterWork();
				hasCameraBtnBeenReleased = true;
			}
		});
	}

	@Override
	public void onResume() {
		super.onResume();
		
		hasCameraBtnBeenReleased = true;
		hasFocusBtnBeenReleased = true;
		disableAllButtons = false;
		amIMaster = false;
		amISlave = false;
		receivedHSVinfo = false;
		receivedMatch = false;
		
		if(CameraSurfaceView.mCamera == null)
		{
			Log.e(TAG, "onResume --> Camera is NULL");
			mCountDownText.setText("");
		}
	}
	
	@Override
	public void onPause() {
		super.onPause();
		
		if(CameraSurfaceView.mCamera == null)
		{
			Log.e(TAG, "onResume --> Camera is NULL");
			mCountDownText.setText("");
		}
	}
	
	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {

		if(disableAllButtons)
			return true;
		
		if(keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_SEARCH)
		{
			hasCameraBtnBeenReleased = true;
			notConnected.cancel();
		}
		else if(keyCode == KeyEvent.KEYCODE_FOCUS)
		{
			CameraSurfaceView.mCamera.cancelAutoFocus();
			hasFocusBtnBeenReleased = true;
		}
		
		
		return (super.onKeyUp(keyCode, event));
	}
	
	private void doTheMasterWork() {
		if(hasCameraBtnBeenReleased && CameraSurfaceView.inPreview)
		{
			if(currentLocation == null)
			{
				noGps.cancel();
				noGps.show();
			}
			else if (mChatService.getState() == BluetoothChatService.STATE_CONNECTED)
			{
				if(!amISlave)
					amIMaster = true;
				
				
				allMyLocations.clear();
				allOtherLocations.clear();
				updateCenterText();
				
				takeMyPicture();
			}
			else
			{
				notConnected.cancel();
				notConnected.show();
			}
		}
	}
	
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {

		if(disableAllButtons)
			return true;
		
		switch(keyCode)
		{
		case KeyEvent.KEYCODE_SEARCH:
			return true;
			
		case KeyEvent.KEYCODE_FOCUS:
		{
			if(hasFocusBtnBeenReleased)
			{
				hasFocusBtnBeenReleased = false;
				CameraSurfaceView.mCamera.autoFocus(null);
				
				return true;
			}
		}

		case KeyEvent.KEYCODE_CAMERA:
			
			doTheMasterWork();
			
			return true;

		default:
			Log.v(TAG, "Button pressed: "+keyCode);
		}

		return(super.onKeyDown(keyCode, event));
	}

	@Override
	protected void handleBluetoothReceivedMsg(Message msg)
	{
		byte[] readBuf = (byte[]) msg.obj;
		String readMessage = new String(readBuf, 0, msg.arg1);

		if(!amIMaster && readMessage.indexOf("[PICTURE_TAKEN]") != -1)
		{
			Log.d(TAG, "Have to take a picture now");
			
			disableAllButtons = true;
			amISlave = true;
			
			centerPanel.blank(true);
			
			StringTokenizer tokens= new StringTokenizer(readMessage," []");

			String s1=(String) tokens.nextElement();
			String s2=(String) tokens.nextElement();
			String s3=(String) tokens.nextElement();

			Log.i(TAG, "Received: " +s1+ " "+s2+ " "+ s3 );

			double lat = Double.parseDouble(s1);
			double lon = Double.parseDouble(s2);
			double or  = Double.parseDouble(s3);

			allMyLocations.clear();
			allOtherLocations.clear();
			allOtherLocations.add(new LatLong(lat, lon, or));
			updateCenterText();
			
			new CountDownTimer(5*1000, 1000) {

			     public void onTick(long millisUntilFinished) {
			    	 if(millisUntilFinished / 1000 == 1)
			    		 mCountDownText.setText("Taking Picture\nDO NOT MOVE!");
			    	 else
			    		 mCountDownText.setText("Picture will be taken in\n"+ millisUntilFinished/1000);
			     }

			     public void onFinish() {
			        mCountDownText.setText("");
			        
			        takeMyPicture();
			     }
			}.start();
		}
		else if(!amISlave && readMessage.indexOf("[SLAVE_GPS]") != -1)
		{
			Log.d(TAG, "slave infos");
			
			StringTokenizer tokens= new StringTokenizer(readMessage," []");

			String s1=(String) tokens.nextElement();
			String s2=(String) tokens.nextElement();
			String s3=(String) tokens.nextElement();

			Log.i(TAG, "Received: " +s1+ " "+s2+ " "+ s3 );

			double lat = Double.parseDouble(s1);
			double lon = Double.parseDouble(s2);
			double or  = Double.parseDouble(s3);
			
			allOtherLocations.clear();
			allOtherLocations.add(new LatLong(lat, lon, or));
			updateCenterText();
		}
		else
		{
			Log.d(TAG, "rec: "+readMessage);
			
			mPatchHist = new HVHistogram(readMessage);
			
			Log.d(TAG, "reparsed: "+ mPatchHist);
			
			receivedHSVinfo = true;
		}
	}

	private void takeMyPicture() {
		if(CameraSurfaceView.mCamera == null)
		{
			Log.e(TAG, "Camera is NULL");
			Toast.makeText(this, "Cannot take a picture. Retry!", Toast.LENGTH_SHORT).show();
			
			mCountDownText.setText("");
			
			return;
		}
		Log.v(TAG, "Last picture: " + lastFile);

		CameraSurfaceView.mCamera.takePicture(mShutterCallback, null, mPictureCallback);

		Log.v(TAG, "Picture done: " + lastFile);
		Log.v(TAG, "Nextfile: " + lastFile);
		
		hasCameraBtnBeenReleased = false;
	}

	private Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {

		@Override
		public void onShutter() {
			
			Log.d(TAG, "onShutter");
			CameraSurfaceView.inPreview = false;
			
			if(amIMaster) 
			{
				sendMessage("["+currentLocation.getLatitude()+" "+currentLocation.getLongitude()+" "+currentOrientation+"][PICTURE_TAKEN]");
				
				allMyLocations.clear();
				allMyLocations.add(new LatLong(currentLocation, currentOrientation));
				updateCenterText();
			}
			else
			{
				waiting.show();
			}
		}
	};

	protected void showToast(String s)
	{
		Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show();
	}
	
	private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
		public void onPictureTaken(byte[] imageData, Camera c) 
		{
			if (imageData != null) 
			{
				Log.d(TAG, "onPictureTaken -> imageData");
				
				new SavePhotoTask().execute(imageData);

				try{
					if(!CameraSurfaceView.inPreview && !amISlave)
					{
						CameraSurfaceView.mCamera.startPreview();
						CameraSurfaceView.inPreview = true;
					}
					else
					{
						// display spinning wheel
						
						while(!receivedMatch)
						{
							try {
								waiting.show();
								Thread.sleep(200);
							} catch (InterruptedException e) {
								Log.e(TAG, e.getMessage());
							}
						}
						
						waiting.cancel();
						
						updateCenterText();
						
						btn_restart.setVisibility(View.VISIBLE);
						
						centerPanel.drawRect(match_x, match_y, mCropWidth, mCropHeight);
						
						receivedMatch = false;
					}
					
					
					
				}catch (Exception e) {
					Log.e(TAG, e.getMessage());
				}
				
				disableAllButtons = false;
			}
		}
	};

	private class SavePhotoTask extends AsyncTask<byte[], String, String> {
		@Override
		protected String doInBackground(byte[]... jpeg) {

			Log.v(TAG, "File to take: " + lastFile);

			File photo=new File(fileDir + fileName + lastFile + fileExt);

			if (photo.exists()) {
				photo.delete();
			}

			try {
				FileOutputStream fos=new FileOutputStream(photo.getPath());

				fos.write(jpeg[0]);
				fos.close();
			}
			catch (java.io.IOException e) {
				Log.e(TAG, "Exception in SavePhotoTask photoCallback", e);
			}
			
//			lastFile++;
			
			if (amIMaster) 
			{
		    	doCropImage(photo);
		    	
		    	amIMaster = false;
		    } 
			else if(amISlave)
			{
		    	compareImage(photo, currentLocation, currentOrientation);
		    	
		    	receivedHSVinfo = false;
		    	amISlave = false;
		    }
			
			return null;
		}
	}
	
	
	public void doCropImage(File file) {
		Log.d(TAG, "entered -- doCropImage() --");
		
		// Scale Bitmap
		Bitmap bitmap = BitmapFactory.decodeFile(file.getPath());
		
		Log.w(TAG, "Width Height: " + bitmap.getWidth() + " " + bitmap.getHeight() +" mScaleWidth: "+mScaleWidth);
		
		mScaleWidthRatio = bitmap.getWidth()/mScaleWidth;
		
		Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, mScaleWidth, mScaleHeight, false);
		
		bitmap.recycle();
		
		// Crop
		Log.i(TAG, "Crop Image");
		int x = scaledBitmap.getWidth()/2-mCropWidth/2;
		int y = scaledBitmap.getHeight()/2-mCropHeight/2;
		Bitmap croppedBitmap = Bitmap.createBitmap(scaledBitmap, x, y, mCropWidth, mCropHeight);
		
		// Write cropped image to file
		try {
			if (croppedBitmap != null) {
				FileOutputStream out = new FileOutputStream(fileDir + "crop.png");
				croppedBitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
			}
		} catch (FileNotFoundException e) {
			Log.e(TAG, e.getMessage());
		}
		
		// Extract crop pixels
		int [] crop_pixels = new int[mCropWidth*mCropHeight];
		croppedBitmap.getPixels(crop_pixels, 0, croppedBitmap.getWidth(), 0, 0, croppedBitmap.getWidth(), croppedBitmap.getHeight());
		
		// Compute Histogram
		Log.i(TAG, "Patch Histogram");
		mPatchHist = new HVHistogram(crop_pixels, mCropWidth, mCropHeight);
		
		Log.i(TAG, ""+mPatchHist);
		Log.i(TAG, "Sending Message: " + mPatchHist.toString() + "[" + mScaleWidthRatio + "]");
		
		if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) 
		{
			Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT).show();
		}
		else
		{		
			sendMessage(mPatchHist.toString() + "[" + mScaleWidthRatio + "]");
		}
		
		croppedBitmap.recycle();
	}
	
	public void compareImage(File file, Location my_loc, double my_or) {
		
		Log.d(TAG, "entered compareImage()");
		
		while(!receivedHSVinfo)
		{
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				Log.e(TAG, e.getMessage());
			}
		}
		
		if (mPatchHist == null) 
		{
			Log.e(TAG, "error! in mPatchHist == null");
			
			return;
		}
		else
		{
			Log.i(TAG, "ready to process the info");
		}
		
		// Scale Bitmap
		Bitmap bitmap = BitmapFactory.decodeFile(file.getPath());
		
		Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, mScaleWidth, mScaleHeight, false);
		
		bitmap.recycle();

		int[] image = new int[scaledBitmap.getWidth()*scaledBitmap.getHeight()];
		scaledBitmap.getPixels(image, 0, scaledBitmap.getWidth(), 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight());
		
		IntegralHVHistogram ihist = new IntegralHVHistogram(image, scaledBitmap.getWidth(), scaledBitmap.getHeight());
		
		int[] match_position = new int[2];
		double m_value = ihist.searchHistogram(match_position, mPatchHist, mSearchStepX, mSearchStepY);
		
		Log.i(TAG, "Best match " + m_value + " @ " +  match_position[0] + " " + match_position[1]);
		
		match_x = match_position[0];
		match_y = match_position[1];
		
		Log.w(TAG, "match_x: " + match_x  +  "a: " + (match_x + mCropWidth/2 - mScaleWidth/2) + " b: " + (mFx/mScaleWidthRatio));
		
		
		double rad_offset = Math.atan((double)(match_x + mCropWidth/2 - mScaleWidth/2)/(mFx/mScaleWidthRatio));
		
		Log.w(TAG, "Radians offset: " + rad_offset);
		
		// update location adding the bearing
		allMyLocations.clear();
		allMyLocations.add(new LatLong(my_loc, my_or+rad_offset));
		
		Log.w(TAG, "my or: " + my_or + " your_orient: " + allOtherLocations.get(0).getOrientation()
				+ " calc_orient: " + (my_or + rad_offset));
		
		receivedMatch = true;
		
		//send the info to the MASTER
		sendMessage("["+my_loc.getLatitude()+" "+my_loc.getLongitude()+" "+(my_or+rad_offset)+"][SLAVE_GPS]");
		
		Bitmap match_bitmap = Bitmap.createBitmap(scaledBitmap, match_position[0], match_position[1], mCropWidth, mCropHeight);
		scaledBitmap.recycle();
		
		try {
			if (match_bitmap != null) {
				FileOutputStream out = new FileOutputStream(fileDir + "match.png");
				match_bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
				match_bitmap.recycle();
			}
		} catch (FileNotFoundException e) {
			Log.e(TAG, e.getMessage());
		}
	}
	
	public static float getHeightRatio()
	{
		return  (float)mCropHeight/mScaleHeight;
	}
	
	public static float getWidthRatio()
	{
		return  (float)mCropWidth/mScaleWidth;
	}
}
